%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2021. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(partisan_gen_event).

%%% 
%%% A general event handler.
%%% Several handlers (functions) can be added.
%%% Each handler holds a state and will be called
%%% for every event received of the handler.
%%% 

%%% Modified by Magnus.
%%%       Take care of fault situations and made notify asynchronous.
%%% Re-written by Joe with new functional interface !
%%% Modified by Martin - uses proc_lib, sys and gen!

%%%
%%% NOTE: If init_ack() return values are modified, see comment
%%%       above monitor_return() in gen.erl!
%%%

-export([start/0, start/1, start/2,
         start_link/0, start_link/1, start_link/2,
         start_monitor/0, start_monitor/1, start_monitor/2,
         stop/1, stop/3,
     notify/2, sync_notify/2,
     add_handler/3, add_sup_handler/3, delete_handler/3, swap_handler/3,
     swap_sup_handler/3, which_handlers/1, call/3, call/4,
         send_request/3, wait_response/2, receive_response/2, check_response/2,
         wake_hib/5]).

-export([init_it/6,
     system_continue/3,
     system_terminate/4,
     system_code_change/4,
     system_get_state/1,
     system_replace_state/2,
     format_status/2]).

%% logger callback
-export([format_log/1, format_log/2]).

-export_type([handler/0, handler_args/0, add_handler_ret/0,
              del_handler_ret/0]).

-record(handler, {module             :: atom(),
          id = false,
          state,
          supervised = false :: 'false' | pid()}).

-include_lib("partisan/include/partisan_logger.hrl").

%%%=========================================================================
%%%  API
%%%=========================================================================

%% gen_event:start(Handler) -> {ok, Pid} | {error, What}
%%   gen_event:add_handler(Handler, Mod, Args) -> ok | Other
%%      gen_event:notify(Handler, Event) -> ok
%%      gen_event:call(Handler, Mod, Query) -> {ok, Val} | {error, Why}
%%      gen_event:call(Handler, Mod, Query, Timeout) -> {ok, Val} | {error, Why}
%%   gen_event:delete_handler(Handler, Mod, Args) -> Val
%%   gen_event:swap_handler(Handler, {OldMod, Args1}, {NewMod, Args2}) -> ok
%%   gen_event:which_handler(Handler) -> [Mod]
%% gen_event:stop(Handler) -> ok 

-callback init(InitArgs :: term()) ->
    {ok, State :: term()} |
    {ok, State :: term(), hibernate} |
    {error, Reason :: term()}.
-callback handle_event(Event :: term(), State :: term()) ->
    {ok, NewState :: term()} |
    {ok, NewState :: term(), hibernate} |
    {swap_handler, Args1 :: term(), NewState :: term(),
     Handler2 :: (atom() | {atom(), Id :: term()}), Args2 :: term()} |
    remove_handler.
-callback handle_call(Request :: term(), State :: term()) ->
    {ok, Reply :: term(), NewState :: term()} |
    {ok, Reply :: term(), NewState :: term(), hibernate} |
    {swap_handler, Reply :: term(), Args1 :: term(), NewState :: term(),
     Handler2 :: (atom() | {atom(), Id :: term()}), Args2 :: term()} |
    {remove_handler, Reply :: term()}.
-callback handle_info(Info :: term(), State :: term()) ->
    {ok, NewState :: term()} |
    {ok, NewState :: term(), hibernate} |
    {swap_handler, Args1 :: term(), NewState :: term(),
     Handler2 :: (atom() | {atom(), Id :: term()}), Args2 :: term()} |
    remove_handler.
-callback terminate(Args :: (term() | {stop, Reason :: term()} |
                             stop | remove_handler |
                             {error, {'EXIT', Reason :: term()}} |
                             {error, term()}),
                    State :: term()) ->
    term().
-callback code_change(OldVsn :: (term() | {down, term()}),
                      State :: term(), Extra :: term()) ->
    {ok, NewState :: term()}.
-callback format_status(Opt, StatusData) -> Status when
      Opt :: 'normal' | 'terminate',
      StatusData :: [PDict | State],
      PDict :: [{Key :: term(), Value :: term()}],
      State :: term(),
      Status :: term().

-optional_callbacks(
    [handle_info/2, terminate/2, code_change/3, format_status/2]).

%%---------------------------------------------------------------------------

-type handler()          :: atom() | {atom(), term()}.
-type handler_args()     :: term().
-type add_handler_ret()  :: ok | term() | {'EXIT',term()}.
-type del_handler_ret()  :: ok | term() | {'EXIT',term()}.

-type emgr_name() :: {'local', atom()} | {'global', term()}
                   | {'via', atom(), term()}.
-type debug_flag() :: 'trace' | 'log' | 'statistics' | 'debug'
                    | {'logfile', string()}.
-type option() :: {'timeout', timeout()}
                | {'debug', [debug_flag()]}
                | {'spawn_opt', [partisan_proc_lib:start_spawn_option()]}
                | {'hibernate_after', timeout()}.
-type emgr_ref()  :: atom() | {atom(), atom()} |  {'global', term()}
                   | {'via', atom(), term()} | pid().
-type start_ret() :: {'ok', pid()} | {'error', term()}.
-type start_mon_ret() :: {'ok', {pid(),reference()}} | {'error', term()}.
-type request_id() :: term().

%%---------------------------------------------------------------------------

-define(NO_CALLBACK, 'no callback module').

%% -----------------------------------------------------------------
%% Starts a generic event handler.
%% start()
%% start(MgrName | Options)
%% start(MgrName, Options)
%% start_link()
%% start_link(MgrName | Options)
%% start_link(MgrName, Options)
%%    MgrName ::= {local, atom()} | {global, term()} | {via, atom(), term()}
%%    Options ::= [{timeout, Timeout} | {debug, [Flag]} | {spawn_opt,SOpts}]
%%       Flag ::= trace | log | {logfile, File} | statistics | debug
%%          (debug == log && statistics)
%% Returns: {ok, Pid} |
%%          {error, {already_started, Pid}} |
%%          {error, Reason}
%% -----------------------------------------------------------------

-spec start() -> start_ret().
start() ->
    partisan_gen:start(?MODULE, nolink, ?NO_CALLBACK, [], []).

-spec start(emgr_name() | [option()]) -> start_ret().
start(Name) when is_tuple(Name) ->
    partisan_gen:start(?MODULE, nolink, Name, ?NO_CALLBACK, [], []);
start(Options) when is_list(Options) ->
    partisan_gen:start(?MODULE, nolink, ?NO_CALLBACK, [], Options).

-spec start(emgr_name(), [option()]) -> start_ret().
start(Name, Options) ->
    partisan_gen:start(?MODULE, nolink, Name, ?NO_CALLBACK, [], Options).

-spec start_link() -> start_ret().
start_link() ->
    partisan_gen:start(?MODULE, link, ?NO_CALLBACK, [], []).

-spec start_link(emgr_name() | [option()]) -> start_ret().
start_link(Name) when is_tuple(Name) ->
    partisan_gen:start(?MODULE, link, Name, ?NO_CALLBACK, [], []);
start_link(Options) when is_list(Options) ->
    partisan_gen:start(?MODULE, link, ?NO_CALLBACK, [], Options).

-spec start_link(emgr_name(), [option()]) -> start_ret().
start_link(Name, Options) ->
    partisan_gen:start(?MODULE, link, Name, ?NO_CALLBACK, [], Options).

-spec start_monitor() -> start_mon_ret().
start_monitor() ->
    partisan_gen:start(?MODULE, monitor, ?NO_CALLBACK, [], []).

-spec start_monitor(emgr_name() | [option()]) -> start_mon_ret().
start_monitor(Name) when is_tuple(Name) ->
    partisan_gen:start(?MODULE, monitor, Name, ?NO_CALLBACK, [], []);
start_monitor(Options) when is_list(Options) ->
    partisan_gen:start(?MODULE, monitor, ?NO_CALLBACK, [], Options).

-spec start_monitor(emgr_name(), [option()]) -> start_mon_ret().
start_monitor(Name, Options) ->
    partisan_gen:start(?MODULE, monitor, Name, ?NO_CALLBACK, [], Options).

%% -spec init_it(pid(), 'self' | pid(), emgr_name(), module(), [term()], [_]) -> 
init_it(Starter, self, Name, Mod, Args, Options) ->
    init_it(Starter, self(), Name, Mod, Args, Options);
init_it(Starter, Parent, Name0, _, _, Options) ->
    process_flag(trap_exit, true),
    Name = partisan_gen:name(Name0),
    Debug = partisan_gen:debug_options(Name, Options),
    HibernateAfterTimeout = partisan_gen:hibernate_after(Options),
    partisan_proc_lib:init_ack(Starter, {ok, self()}),
    loop(Parent, Name, [], HibernateAfterTimeout, Debug, false).

-spec add_handler(emgr_ref(), handler(), term()) -> term().
add_handler(M, Handler, Args) -> rpc(M, {add_handler, Handler, Args}).

-spec add_sup_handler(emgr_ref(), handler(), term()) -> term().
add_sup_handler(M, Handler, Args)  ->
    rpc(M, {add_sup_handler, Handler, Args, self()}).

-spec notify(emgr_ref(), term()) -> 'ok'.
notify(M, Event) -> send(M, {notify, Event}).

-spec sync_notify(emgr_ref(), term()) -> 'ok'.
sync_notify(M, Event) -> rpc(M, {sync_notify, Event}).

-spec call(emgr_ref(), handler(), term()) -> term().
call(M, Handler, Query) -> call1(M, Handler, Query).

-spec call(emgr_ref(), handler(), term(), timeout()) -> term().
call(M, Handler, Query, Timeout) -> call1(M, Handler, Query, Timeout).

-spec send_request(emgr_ref(), handler(), term()) -> request_id().
send_request(M, Handler, Query) ->
    partisan_gen:send_request(M, self(), {call, Handler, Query}).

-spec wait_response(RequestId::request_id(), timeout()) ->
        {reply, Reply::term()} | 'timeout' | {error, {Reason::term(), emgr_ref()}}.
wait_response(RequestId, Timeout) ->
    case partisan_gen:wait_response(RequestId, Timeout) of
        {reply, {error, _} = Err} -> Err;
        Return -> Return
    end.

-spec receive_response(RequestId::request_id(), timeout()) ->
        {reply, Reply::term()} | 'timeout' | {error, {Reason::term(), emgr_ref()}}.
receive_response(RequestId, Timeout) ->
    case partisan_gen:receive_response(RequestId, Timeout) of
        {reply, {error, _} = Err} -> Err;
        Return -> Return
    end.

-spec check_response(Msg::term(), RequestId::request_id()) ->
        {reply, Reply::term()} | 'no_reply' | {error, {Reason::term(), emgr_ref()}}.
check_response(Msg, RequestId) ->
    case partisan_gen:check_response(Msg, RequestId)  of
        {reply, {error, _} = Err} -> Err;
        Return -> Return
    end.

-spec delete_handler(emgr_ref(), handler(), term()) -> term().
delete_handler(M, Handler, Args) -> rpc(M, {delete_handler, Handler, Args}).

-spec swap_handler(emgr_ref(), {handler(), term()}, {handler(), term()}) ->
        'ok' | {'error', term()}.
swap_handler(M, {H1, A1}, {H2, A2}) -> rpc(M, {swap_handler, H1, A1, H2, A2}).

-spec swap_sup_handler(emgr_ref(), {handler(), term()}, {handler(), term()}) ->
        'ok' | {'error', term()}.
swap_sup_handler(M, {H1, A1}, {H2, A2}) ->
    rpc(M, {swap_sup_handler, H1, A1, H2, A2, self()}).

-spec which_handlers(emgr_ref()) -> [handler()].
which_handlers(M) -> rpc(M, which_handlers).

-spec stop(emgr_ref()) -> 'ok'.
stop(M) ->
    partisan_gen:stop(M).

stop(M, Reason, Timeout) ->
    partisan_gen:stop(M, Reason, Timeout).

rpc(M, Cmd) ->
    {ok, Reply} = partisan_gen:call(M, self(), Cmd, infinity),
    Reply.

call1(M, Handler, Query) ->
    Cmd = {call, Handler, Query},
    try partisan_gen:call(M, self(), Cmd) of
    {ok, Res} ->
        Res
    catch
    exit:Reason ->
        exit({Reason, {?MODULE, call, [M, Handler, Query]}})
    end.

call1(M, Handler, Query, Timeout) ->
    Cmd = {call, Handler, Query},
    try partisan_gen:call(M, self(), Cmd, Timeout) of
    {ok, Res} ->
        Res
    catch
    exit:Reason ->
        exit({Reason, {?MODULE, call, [M, Handler, Query, Timeout]}})
    end.

send({global, Name}, Cmd) ->
    catch global:send(Name, Cmd),
    ok;
send({via, Mod, Name}, Cmd) ->
    catch Mod:send(Name, Cmd),
    ok;
send(M, Cmd) ->
    M ! Cmd,
    ok.

loop(Parent, ServerName, MSL, HibernateAfterTimeout, Debug, true) ->
     partisan_proc_lib:hibernate(?MODULE, wake_hib, [Parent, ServerName, MSL, HibernateAfterTimeout, Debug]);
loop(Parent, ServerName, MSL, HibernateAfterTimeout, Debug, _) ->
    fetch_msg(Parent, ServerName, MSL, HibernateAfterTimeout, Debug, false).

wake_hib(Parent, ServerName, MSL, HibernateAfterTimeout, Debug) ->
    fetch_msg(Parent, ServerName, MSL, HibernateAfterTimeout, Debug, true).

fetch_msg(Parent, ServerName, MSL, HibernateAfterTimeout, Debug, Hib) ->
    receive
    {system, From, Req} ->
        partisan_sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
                  [ServerName, MSL, HibernateAfterTimeout, Hib],Hib);
    {'EXIT', Parent, Reason} ->
        terminate_server(Reason, Parent, MSL, ServerName);
    Msg when Debug =:= [] ->
        handle_msg(Msg, Parent, ServerName, MSL, HibernateAfterTimeout, []);
    Msg ->
        Debug1 = partisan_sys:handle_debug(Debug, fun print_event/3,
                      ServerName, {in, Msg}),
        handle_msg(Msg, Parent, ServerName, MSL, HibernateAfterTimeout, Debug1)
    after HibernateAfterTimeout ->
        loop(Parent, ServerName, MSL, HibernateAfterTimeout, Debug, true)
    end.

handle_msg(Msg, Parent, ServerName, MSL, HibernateAfterTimeout, Debug) ->
    case Msg of
    {notify, Event} ->
        {Hib,MSL1} = server_notify(Event, handle_event, MSL, ServerName),
        loop(Parent, ServerName, MSL1, HibernateAfterTimeout, Debug, Hib);
    {_From, Tag, {sync_notify, Event}} ->
        {Hib, MSL1} = server_notify(Event, handle_event, MSL, ServerName),
        reply(Tag, ok),
        loop(Parent, ServerName, MSL1, HibernateAfterTimeout, Debug, Hib);
    {'EXIT', From, Reason} ->
        MSL1 = handle_exit(From, Reason, MSL, ServerName),
        loop(Parent, ServerName, MSL1, HibernateAfterTimeout, Debug, false);
    {_From, Tag, {call, Handler, Query}} ->
        {Hib, Reply, MSL1} = server_call(Handler, Query, MSL, ServerName),
        reply(Tag, Reply),
        loop(Parent, ServerName, MSL1, HibernateAfterTimeout, Debug, Hib);
    {_From, Tag, {add_handler, Handler, Args}} ->
        {Hib, Reply, MSL1} = server_add_handler(Handler, Args, MSL),
        reply(Tag, Reply),
        loop(Parent, ServerName, MSL1, HibernateAfterTimeout, Debug, Hib);
    {_From, Tag, {add_sup_handler, Handler, Args, SupP}} ->
        {Hib, Reply, MSL1} = server_add_sup_handler(Handler, Args, MSL, SupP),
        reply(Tag, Reply),
        loop(Parent, ServerName, MSL1, HibernateAfterTimeout, Debug, Hib);
    {_From, Tag, {delete_handler, Handler, Args}} ->
        {Reply, MSL1} = server_delete_handler(Handler, Args, MSL,
                          ServerName),
        reply(Tag, Reply),
        loop(Parent, ServerName, MSL1, HibernateAfterTimeout, Debug, false);
    {_From, Tag, {swap_handler, Handler1, Args1, Handler2, Args2}} ->
        {Hib, Reply, MSL1} = server_swap_handler(Handler1, Args1, Handler2,
                             Args2, MSL, ServerName),
        reply(Tag, Reply),
        loop(Parent, ServerName, MSL1, HibernateAfterTimeout, Debug, Hib);
    {_From, Tag, {swap_sup_handler, Handler1, Args1, Handler2, Args2,
             Sup}} ->
        {Hib, Reply, MSL1} = server_swap_handler(Handler1, Args1, Handler2,
                        Args2, MSL, Sup, ServerName),
        reply(Tag, Reply),
        loop(Parent, ServerName, MSL1, HibernateAfterTimeout, Debug, Hib);
    {_From, Tag, stop} ->
        catch terminate_server(normal, Parent, MSL, ServerName),
        reply(Tag, ok);
    {_From, Tag, which_handlers} ->
        reply(Tag, the_handlers(MSL)),
        loop(Parent, ServerName, MSL, HibernateAfterTimeout, Debug, false);
    {_From, Tag, get_modules} ->
        reply(Tag, get_modules(MSL)),
        loop(Parent, ServerName, MSL, HibernateAfterTimeout, Debug, false);
    Other  ->
        {Hib, MSL1} = server_notify(Other, handle_info, MSL, ServerName),
        loop(Parent, ServerName, MSL1, HibernateAfterTimeout, Debug, Hib)
    end.

terminate_server(Reason, Parent, MSL, ServerName) ->
    stop_handlers(MSL, ServerName),
    do_unlink(Parent, MSL),
    exit(Reason).

reply(From, Reply) ->
    partisan_gen:reply(From, Reply).

%% unlink the supervisor process of all supervised handlers.
%% We do not want a handler supervisor to EXIT due to the
%% termination of the event manager (server).
%% Do not unlink Parent !
do_unlink(Parent, MSL) ->
    lists:foreach(fun(Handler) when Handler#handler.supervised =:= Parent ->
              true;
             (Handler) when is_pid(Handler#handler.supervised) ->
              unlink(Handler#handler.supervised),
              true;
             (_) ->
              true
          end,
          MSL).

%% First terminate the supervised (if exists) handlers and
%% then inform other handlers.
%% We do not know if any handler really is interested but it
%% may be so !
handle_exit(From, Reason, MSL, SName) ->
    MSL1 = terminate_supervised(From, Reason, MSL, SName),
    {_,MSL2}=server_notify({'EXIT', From, Reason}, handle_info, MSL1, SName),
    MSL2.

terminate_supervised(Pid, Reason, MSL, SName) ->
    F = fun(Ha) when Ha#handler.supervised =:= Pid ->
        do_terminate(Ha#handler.module,
                 Ha,
                 {stop,Reason},
                 Ha#handler.state,
                 {parent_terminated, {Pid,Reason}},
                 SName,
                 shutdown),
        false;
       (_) ->
        true
    end,
    lists:filter(F, MSL).

%%-----------------------------------------------------------------
%% Callback functions for system messages handling.
%%-----------------------------------------------------------------
system_continue(Parent, Debug, [ServerName, MSL, HibernateAfterTimeout, Hib]) ->
    loop(Parent, ServerName, MSL, HibernateAfterTimeout, Debug, Hib).

-spec system_terminate(_, _, _, [_]) -> no_return().
system_terminate(Reason, Parent, _Debug, [ServerName, MSL, _HibernateAfterTimeout, _Hib]) ->
    terminate_server(Reason, Parent, MSL, ServerName).

%%-----------------------------------------------------------------
%% Module here is sent in the system msg change_code.  It specifies
%% which module should be changed.
%%-----------------------------------------------------------------
system_code_change([ServerName, MSL, HibernateAfterTimeout, Hib], Module, OldVsn, Extra) ->
    MSL1 = lists:zf(fun(H) when H#handler.module =:= Module ->
                {ok, NewState} =
                Module:code_change(OldVsn,
                           H#handler.state, Extra),
                {true, H#handler{state = NewState}};
               (_) -> true
            end,
            MSL),
    {ok, [ServerName, MSL1, HibernateAfterTimeout, Hib]}.

system_get_state([_ServerName, MSL, _HibernateAfterTimeout, _Hib]) ->
    {ok, [{Mod,Id,State} || #handler{module=Mod, id=Id, state=State} <- MSL]}.

system_replace_state(StateFun, [ServerName, MSL, HibernateAfterTimeout, Hib]) ->
    {NMSL, NStates} =
        lists:unzip([begin
                 Cur = {Mod,Id,State},
                 try
                     NState = {Mod,Id,NS} = StateFun(Cur),
                     {HS#handler{state=NS}, NState}
                 catch
                     _:_ ->
                     {HS, Cur}
                 end
                 end || #handler{module=Mod, id=Id, state=State}=HS <- MSL]),
    {ok, NStates, [ServerName, NMSL, HibernateAfterTimeout, Hib]}.

%%-----------------------------------------------------------------
%% Format debug messages.  Print them as the call-back module sees
%% them, not as the real erlang messages.  Use trace for that.
%%-----------------------------------------------------------------
print_event(Dev, {in, Msg}, Name) ->
    case Msg of
    {notify, Event} ->
        io:format(Dev, "*DBG* ~tp got event ~tp~n", [Name, Event]);
    {_,_,{call, Handler, Query}} ->
        io:format(Dev, "*DBG* ~tp(~tp) got call ~tp~n",
              [Name, Handler, Query]);
    _ ->
        io:format(Dev, "*DBG* ~tp got ~tp~n", [Name, Msg])
    end;
print_event(Dev, Dbg, Name) ->
    io:format(Dev, "*DBG* ~tp : ~tp~n", [Name, Dbg]).


%% server_add_handler(Handler, Args, MSL) -> {Ret, MSL'}.
%%   where MSL = [#handler{}]
%%   Ret goes to the top level MSL' is the new internal state of the
%%   event handler

server_add_handler({Mod,Id}, Args, MSL) ->
    Handler = #handler{module = Mod,
               id = Id},
    server_add_handler(Mod, Handler, Args, MSL);
server_add_handler(Mod, Args, MSL) ->
    Handler = #handler{module = Mod},
    server_add_handler(Mod, Handler, Args, MSL).

server_add_handler(Mod, Handler, Args, MSL) ->
    case catch Mod:init(Args) of
        {ok, State} ->
        {false, ok, [Handler#handler{state = State}|MSL]};
        {ok, State, hibernate} ->
        {true, ok, [Handler#handler{state = State}|MSL]};
        Other ->
            {false, Other, MSL}
    end.

%% Set up a link to the supervising process.
%% (Ought to be unidirected links here, Erl5.0 !!)
%% NOTE: This link will not be removed then the
%% handler is removed in case another handler has
%% own link to this process.
server_add_sup_handler({Mod,Id}, Args, MSL, Parent) ->
    link(Parent),
    Handler = #handler{module = Mod,
               id = Id,
               supervised = Parent},
    server_add_handler(Mod, Handler, Args, MSL);
server_add_sup_handler(Mod, Args, MSL, Parent) ->
    link(Parent),
    Handler = #handler{module = Mod,
               supervised = Parent},
    server_add_handler(Mod, Handler, Args, MSL).

%% server_delete_handler(HandlerId, Args, MSL) -> {Ret, MSL'}

server_delete_handler(HandlerId, Args, MSL, SName) ->
    case split(HandlerId, MSL) of
    {Mod, Handler, MSL1} ->
        {do_terminate(Mod, Handler, Args,
              Handler#handler.state, delete, SName, normal),
         MSL1};
    error ->
        {{error, module_not_found}, MSL}
    end.

%% server_swap_handler(Handler1, Args1, Handler2, Args2, MSL, SN) -> MSL'
%% server_swap_handler(Handler1, Args1, Handler2, Args2, MSL, Sup, SN) -> MSL'

server_swap_handler(Handler1, Args1, Handler2, Args2, MSL, SName) ->
    {State2, Sup, MSL1} = split_and_terminate(Handler1, Args1, MSL,
                          SName, Handler2, false),
    case s_s_h(Sup, Handler2, {Args2, State2}, MSL1) of
    {Hib, ok, MSL2} ->
        {Hib, ok, MSL2};
    {Hib, What, MSL2} ->
        {Hib, {error, What}, MSL2}
    end.

server_swap_handler(Handler1, Args1, Handler2, Args2, MSL, Sup, SName) ->
    {State2, _, MSL1} = split_and_terminate(Handler1, Args1, MSL,
                        SName, Handler2, Sup),
    case s_s_h(Sup, Handler2, {Args2, State2}, MSL1) of
    {Hib, ok, MSL2} ->
        {Hib, ok, MSL2};
    {Hib, What, MSL2} ->
        {Hib, {error, What}, MSL2}
    end.

s_s_h(false, Handler, Args, MSL) ->
    server_add_handler(Handler, Args, MSL);
s_s_h(Pid, Handler, Args, MSL) ->
    server_add_sup_handler(Handler, Args, MSL, Pid).

split_and_terminate(HandlerId, Args, MSL, SName, Handler2, Sup) ->
    case split(HandlerId, MSL) of
    {Mod, Handler, MSL1} ->
        OldSup = Handler#handler.supervised,
        NewSup = if
             not Sup -> OldSup;
             true    -> Sup
             end,
        {do_terminate(Mod, Handler, Args,
              Handler#handler.state, swapped, SName,
              {swapped, Handler2, NewSup}),
         OldSup,
         MSL1};
    error ->
            {error, false, MSL}
    end.

%% server_notify(Event, Func, MSL, SName) -> MSL'

server_notify(Event, Func, [Handler|T], SName) ->
    case server_update(Handler, Func, Event, SName) of
    {ok, Handler1} ->
        {Hib, NewHandlers} = server_notify(Event, Func, T, SName),
        {Hib, [Handler1|NewHandlers]};
    {hibernate, Handler1} ->
        {_Hib, NewHandlers} = server_notify(Event, Func, T, SName),
        {true, [Handler1|NewHandlers]};
    no ->
        server_notify(Event, Func, T, SName)
    end;
server_notify(_, _, [], _) ->
    {false, []}.

%% server_update(Handler, Func, Event, ServerName) -> Handler1 | no

server_update(Handler1, Func, Event, SName) ->
    Mod1 = Handler1#handler.module,
    State = Handler1#handler.state,
    case catch Mod1:Func(Event, State) of
    {ok, State1} ->
        {ok, Handler1#handler{state = State1}};
    {ok, State1, hibernate} ->
        {hibernate, Handler1#handler{state = State1}};
    {swap_handler, Args1, State1, Handler2, Args2} ->
        do_swap(Mod1, Handler1, Args1, State1, Handler2, Args2, SName);
    remove_handler ->
        do_terminate(Mod1, Handler1, remove_handler, State,
             remove, SName, normal),
        no;
        {'EXIT', {undef, [{Mod1, handle_info, [_,_], _}|_]}} ->
            ?LOG_WARNING(#{label=>{gen_event,no_handle_info},
                           module=>Mod1,
                           message=>Event},
                         #{domain=>[otp],
                           report_cb=>fun gen_event:format_log/2,
                           error_logger=>
                               #{tag=>warning_msg, % warningmap??
                                 report_cb=>fun gen_event:format_log/1}}),
            {ok, Handler1};
    Other ->
        do_terminate(Mod1, Handler1, {error, Other}, State,
             Event, SName, crash),
        no
    end.

do_swap(Mod1, Handler1, Args1, State1, Handler2, Args2, SName) ->
    %% finalise the existing handler
    State2 = do_terminate(Mod1, Handler1, Args1, State1,
              swapped, SName,
              {swapped, Handler2, Handler1#handler.supervised}),
    {Mod2, Handler} = new_handler(Handler2, Handler1),
    case catch Mod2:init({Args2, State2}) of
    {ok, State2a} ->
        {ok, Handler#handler{state = State2a}};
    Other ->
        report_terminate(Handler, crash, {error, Other}, SName, false),
        no
    end.

new_handler({Mod,Id}, Handler1) ->
    {Mod, #handler{module = Mod,
           id = Id,
           supervised = Handler1#handler.supervised}};
new_handler(Mod, Handler1) ->
    {Mod, #handler{module = Mod,
           supervised = Handler1#handler.supervised}}.


-spec split(handler(), [#handler{}]) ->
    {atom(), #handler{}, [#handler{}]} | 'error'.

split(Ha, MSL) -> split(Ha, MSL, []).

split({Mod,Id}, [Ha|T], L) when Ha#handler.module =:= Mod,
                                Ha#handler.id =:= Id ->
    {Mod, Ha, lists:reverse(L, T)};
split(Mod, [Ha|T], L) when Ha#handler.module =:= Mod,
                           not Ha#handler.id ->
    {Mod, Ha, lists:reverse(L, T)};
split(Ha, [H|T], L) ->
    split(Ha, T, [H|L]);
split(_, [], _) ->
    error.

%% server_call(Handler, Query, MSL, ServerName) ->
%%    {Reply, MSL1}

server_call(Handler, Query, MSL, SName) ->
    case search(Handler, MSL) of
    {ok, Ha} ->
        case server_call_update(Ha, Query, SName) of
        {no, Reply} ->
            {false, Reply, delete(Handler, MSL)};
        {{ok, Ha1}, Reply} ->
            {false, Reply, replace(Handler, MSL, Ha1)};
        {{hibernate, Ha1}, Reply} ->
            {true, Reply, replace(Handler, MSL, Ha1)}
        end;
    false ->
        {false, {error, bad_module}, MSL}
    end.

search({Mod, Id}, [Ha|_MSL]) when Ha#handler.module =:= Mod,
                  Ha#handler.id =:= Id ->
    {ok, Ha};
search(Mod, [Ha|_MSL]) when Ha#handler.module =:= Mod,
                not Ha#handler.id ->
    {ok, Ha};
search(Handler, [_|MSL]) ->
    search(Handler, MSL);
search(_, []) ->
    false.

delete({Mod, Id}, [Ha|MSL]) when Ha#handler.module =:= Mod,
                                 Ha#handler.id =:= Id ->
    MSL;
delete(Mod, [Ha|MSL]) when Ha#handler.module =:= Mod,
                           not Ha#handler.id ->
    MSL;
delete(Handler, [Ha|MSL]) ->
    [Ha|delete(Handler, MSL)];
delete(_, []) ->
    [].

replace({Mod, Id}, [Ha|MSL], NewHa) when Ha#handler.module =:= Mod,
                                         Ha#handler.id =:= Id ->
    [NewHa|MSL];
replace(Mod, [Ha|MSL], NewHa) when Ha#handler.module =:= Mod,
                                   not Ha#handler.id ->
    [NewHa|MSL];
replace(Handler, [Ha|MSL], NewHa) ->
    [Ha|replace(Handler, MSL, NewHa)];
replace(_, [], NewHa) ->
    [NewHa].

%% server_call_update(Handler, Query, ServerName) ->
%%    {{Handler1, State1} | 'no', Reply}

server_call_update(Handler1, Query, SName) ->
    Mod1 = Handler1#handler.module,
    State = Handler1#handler.state,
    case catch Mod1:handle_call(Query, State) of
    {ok, Reply, State1} ->
        {{ok, Handler1#handler{state = State1}}, Reply};
    {ok, Reply, State1, hibernate} ->
        {{hibernate, Handler1#handler{state = State1}},
         Reply};
    {swap_handler, Reply, Args1, State1, Handler2, Args2} ->
        {do_swap(Mod1,Handler1,Args1,State1,Handler2,Args2,SName), Reply};
    {remove_handler, Reply} ->
        do_terminate(Mod1, Handler1, remove_handler, State,
             remove, SName, normal),
        {no, Reply};
    Other ->
        do_terminate(Mod1, Handler1, {error, Other}, State,
             Query, SName, crash),
        {no, {error, Other}}
    end.

do_terminate(Mod, Handler, Args, State, LastIn, SName, Reason) ->
    case erlang:function_exported(Mod, terminate, 2) of
    true ->
        Res = (catch Mod:terminate(Args, State)),
        report_terminate(Handler, Reason, Args, State, LastIn, SName, Res),
        Res;
    false ->
        report_terminate(Handler, Reason, Args, State, LastIn, SName, ok),
        ok
    end.

report_terminate(Handler, crash, {error, Why}, State, LastIn, SName, _) ->
    report_terminate(Handler, Why, State, LastIn, SName);
report_terminate(Handler, How, _, State, LastIn, SName, _) ->
    %% How == normal | shutdown | {swapped, NewHandler, NewSupervisor}
    report_terminate(Handler, How, State, LastIn, SName).

report_terminate(Handler, Reason, State, LastIn, SName) ->
    report_error(Handler, Reason, State, LastIn, SName),
    case Handler#handler.supervised of
    false ->
        ok;
    Pid ->
        Pid ! {gen_event_EXIT,handler(Handler),Reason},
        ok
    end.

report_error(_Handler, normal, _, _, _)             -> ok;
report_error(_Handler, shutdown, _, _, _)           -> ok;
report_error(_Handler, {swapped,_,_}, _, _, _)      -> ok;
report_error(Handler, Reason, State, LastIn, SName) ->
    ?LOG_ERROR(#{label=>{gen_event,terminate},
                 handler=>handler(Handler),
                 name=>SName,
                 last_message=>LastIn,
                 state=>format_status(terminate,Handler#handler.module,
                                      get(),State),
                 reason=>Reason},
               #{domain=>[otp],
                 report_cb=>fun gen_event:format_log/2,
                 error_logger=>#{tag=>error,
                                 report_cb=>fun gen_event:format_log/1}}).

%% format_log/1 is the report callback used by Logger handler
%% error_logger only. It is kept for backwards compatibility with
%% legacy error_logger event handlers. This function must always
%% return {Format,Args} compatible with the arguments in this module's
%% calls to error_logger prior to OTP-21.0.
format_log(Report) ->
    Depth = error_logger:get_format_depth(),
    FormatOpts = #{chars_limit => unlimited,
                   depth => Depth,
                   single_line => false,
                   encoding => utf8},
    format_log_multi(limit_report(Report, Depth), FormatOpts).

limit_report(Report, unlimited) ->
    Report;
limit_report(#{label:={gen_event,terminate},
               last_message:=LastIn,
               state:=State,
               reason:=Reason}=Report,
             Depth) ->
    Report#{last_message => io_lib:limit_term(LastIn, Depth),
            state => io_lib:limit_term(State, Depth),
            reason => io_lib:limit_term(Reason, Depth)};
limit_report(#{label:={gen_event,no_handle_info},
               message:=Msg}=Report,
             Depth) ->
    Report#{message => io_lib:limit_term(Msg, Depth)}.

%% format_log/2 is the report callback for any Logger handler, except
%% error_logger.
format_log(Report, FormatOpts0) ->
    Default = #{chars_limit => unlimited,
                depth => unlimited,
                single_line => false,
                encoding => utf8},
    FormatOpts = maps:merge(Default, FormatOpts0),
    IoOpts =
        case FormatOpts of
            #{chars_limit:=unlimited} ->
                [];
            #{chars_limit:=Limit} ->
                [{chars_limit,Limit}]
        end,
    {Format,Args} = format_log_single(Report, FormatOpts),
    io_lib:format(Format, Args, IoOpts).

format_log_single(#{label:={gen_event,terminate},
                    handler:=Handler,
                    name:=SName,
                    last_message:=LastIn,
                    state:=State,
                    reason:=Reason},
                  #{single_line:=true, depth:=Depth}=FormatOpts) ->
    P = p(FormatOpts),
    Reason1 = fix_reason(Reason),
    Format1 = lists:append(["Generic event handler ",P," crashed. "
                            "Installed: ",P,". Last event: ",P,
                            ". State: ",P,". Reason: ",P,"."]),
    Args1 =
        case Depth of
            unlimited ->
                [Handler,SName,Reason1,LastIn,State];
            _ ->
                [Handler,Depth,SName,Depth,Reason1,Depth,
                 LastIn,Depth,State,Depth]
        end,
    {Format1, Args1};
format_log_single(#{label:={gen_event,no_handle_info},
                    module:=Mod,
                    message:=Msg},
                  #{single_line:=true,depth:=Depth}=FormatOpts) ->
    P = p(FormatOpts),
    Format = lists:append(["Undefined handle_info in ",P,
                           ". Unhandled message: ",P,"."]),
    Args =
        case Depth of
            unlimited ->
                [Mod,Msg];
            _ ->
                [Mod,Depth,Msg,Depth]
        end,
    {Format,Args};
format_log_single(Report,FormatOpts) ->
    format_log_multi(Report,FormatOpts).

format_log_multi(#{label:={gen_event,terminate},
                   handler:=Handler,
                   name:=SName,
                   last_message:=LastIn,
                   state:=State,
                   reason:=Reason},
                 #{depth:=Depth}=FormatOpts) ->
    Reason1 = fix_reason(Reason),
    P = p(FormatOpts),
    Format =
        lists:append(["** gen_event handler ",P," crashed.\n",
                      "** Was installed in ",P,"\n",
                      "** Last event was: ",P,"\n",
                      "** When handler state == ",P,"\n",
                      "** Reason == ",P,"\n"]),
    Args =
        case Depth of
            unlimited ->
                [Handler,SName,LastIn,State,Reason1];
            _ ->
                [Handler,Depth,SName,Depth,LastIn,Depth,State,Depth,
                 Reason1,Depth]
        end,
    {Format,Args};
format_log_multi(#{label:={gen_event,no_handle_info},
                   module:=Mod,
                   message:=Msg},
                 #{depth:=Depth}=FormatOpts) ->
    P = p(FormatOpts),
    Format =
        "** Undefined handle_info in ~p\n"
        "** Unhandled message: "++P++"\n",
    Args =
        case Depth of
            unlimited ->
                [Mod,Msg];
            _ ->
                [Mod,Msg,Depth]
        end,
    {Format,Args}.

fix_reason({'EXIT',{undef,[{M,F,A,_L}|_]=MFAs}=Reason}) ->
    case code:is_loaded(M) of
        false ->
            {'module could not be loaded',MFAs};
        _ ->
            case erlang:function_exported(M, F, length(A)) of
                true ->
                    Reason;
                false ->
                    {'function not exported',MFAs}
            end
    end;
fix_reason({'EXIT',Reason}) ->
    Reason;
fix_reason(Reason) ->
    Reason.

p(#{single_line:=Single,depth:=Depth,encoding:=Enc}) ->
    "~"++single(Single)++mod(Enc)++p(Depth);
p(unlimited) ->
    "p";
p(_Depth) ->
    "P".

single(true) -> "0";
single(false) -> "".

mod(latin1) -> "";
mod(_) -> "t".

handler(Handler) when not Handler#handler.id ->
    Handler#handler.module;
handler(Handler) ->
    {Handler#handler.module, Handler#handler.id}.

the_handlers(MSL) ->
    [handler(Handler) || Handler <- MSL].

%% stop_handlers(MSL, ServerName) -> []

stop_handlers([Handler|T], SName) ->
    Mod = Handler#handler.module,
    do_terminate(Mod, Handler, stop, Handler#handler.state,
         stop, SName, shutdown),
    stop_handlers(T, SName);
stop_handlers([], _) ->
    [].

%% Message from the release_handler.
%% The list of modules got to be a set, i.e. no duplicate elements!
get_modules(MSL) ->
    Mods = [Handler#handler.module || Handler <- MSL],
    ordsets:to_list(ordsets:from_list(Mods)).

%%-----------------------------------------------------------------
%% Status information
%%-----------------------------------------------------------------
format_status(Opt, StatusData) ->
    [PDict, SysState, Parent, _Debug, [ServerName, MSL, _HibernateAfterTimeout, _Hib]] = StatusData,
    Header = partisan_gen:format_status_header("Status for event handler",
                                      ServerName),
    FmtMSL = [MS#handler{state=format_status(Opt, Mod, PDict, State)}
              || #handler{module = Mod, state = State} = MS <- MSL],
    [{header, Header},
     {data, [{"Status", SysState},
         {"Parent", Parent}]},
     {items, {"Installed handlers", FmtMSL}}].

format_status(Opt, Mod, PDict, State) ->
    case erlang:function_exported(Mod, format_status, 2) of
        true ->
            Args = [PDict, State],
            case catch Mod:format_status(Opt, Args) of
                {'EXIT', _} -> State;
                Else -> Else
            end;
        false ->
            State
    end.